package onlinebanking.server;

import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;

import onlinebanking.security.probabilistic.GMReinforcedNetworkSwitch;
import onlinebanking.server.modules.*;
import onlinebanking.network.Message;
import onlinebanking.network.NetworkSwitch;
import onlinebanking.network.SimpleNetworkSwitch;
import onlinebanking.server.objects.Session;
import onlinebanking.constants.*;

/**
 * An Engine that handles every client connection
 * 
 * @author Del Mundo
 * 
 */
public class ServerEngine extends Thread {
	/**
	 * Client's IP address. Used as a key in clientlist in SimpleServer
	 */
	private String clientIP;
	/**
	 * Network connection
	 */
	private NetworkSwitch networkswitch;
	/**
	 * Session variable. Stores temporary variables to the current client.
	 */
	private Session session;

	/**
	 * Reference to the Bank Server.
	 */
	private SimpleServer server;

	/**
	 * Creates a new ServerEngine for an incoming client
	 * 
	 * @param s
	 *            Socket of the incoming client
	 * @throws IOException
	 */
	protected ServerEngine(Socket s) {
		NetworkSwitch pointer;
		this.networkswitch = new SimpleNetworkSwitch(s);
		pointer = this.networkswitch;
		// TODO ENCRYPT
		try {

			this.networkswitch = new GMReinforcedNetworkSwitch(
					this.networkswitch);
		} catch (IOException e) {
			this.networkswitch = pointer;
			e.printStackTrace();
		}
	}

	public void setServer(SimpleServer simpleserver) {
		this.server = simpleserver;
	}

	/**
	 * Creates new session
	 * 
	 * @return Session created
	 */
	public Session createNewSession() {
		this.session = new Session();
		return this.session;
	}

	/**
	 * Overrides the run method in the Thread class. Waits for incoming messages
	 * from the client. Disconnects the client when it crashed and removes it
	 * from the clientlist.
	 */
	public void run() {
		Message incommingMessage = null;
		do {
			try {

				String incommingMessageString = networkswitch.receive();
				incommingMessage = new Message(incommingMessageString);
				interpretIncomingMsg(incommingMessage);
			} catch (IOException e) {
				System.err.println("Exception Caught: Client Crashed " + e);
				this.server.updateDataObject(
						"Error: Exception Caught - Client Crashed "
								+ e.toString(), null, null);
				// // e.printStackTrace();
				break;
			} catch (Exception e){
				this.server.updateDataObject(
						"Error: Exception Caught - Client Crashed "
								+ e.toString(), null, null);
			}

		} while (incommingMessage != null
				|| incommingMessage.getMsgType() != MSG.DISCONNECT_REQUEST);
		System.out.println("ServerEngine: " + clientIP + " disconnected.");
		this.server.updateDataObject("ServerEngine: " + clientIP
				+ " disconnected.", null, null);
		this.server.getClientList().remove(this.clientIP); // remove client from
															// list
	}

	/**
	 * Interprets the message received from the client. Identifies the right
	 * module for the client request. Responds to the client's request based on
	 * the output of the module
	 * 
	 * @param message
	 *            Message received from the client
	 * @throws Exception 
	 */
	protected void interpretIncomingMsg(Message message) throws Exception {
		int messagetype = message.getMsgType();

		// only login messages are allowed when not logged in
		if (!isLoggedIn()
				&& (messagetype != MSG.LOGIN_REQUEST && messagetype != MSG.ANNOUNCEMENTS)) {
			Message notloggedinmsg = new Message();
			notloggedinmsg.setMsgType(MSG.USER_NOT_LOGGED_IN);
			this.sendMessage(notloggedinmsg);
			return;
		}

		switch (messagetype) {

		case MSG.LOGIN_REQUEST:
			// new login module
			Login login = new Login(this, message);
			this.sendMessage(login.handleRequest());
			this.server.updateDataObject(
					"User: " + message.getString("username")
							+ " has successfully logged in", null, null);
			break;

		case MSG.LOGOUT_REQUEST:
			// clear session lang :P
			this.session = null;
			Message response = new Message();
			response.setMsgType(MSG.LOGOUT_SUCCESS);
			this.sendMessage(response);
			this.server.updateDataObject(
					"User: " + message.getString("username")
							+ " has successfully logged out", null, null);
			break;

		case MSG.MY_BANK_ACCOUNTS:
			SelectActiveAccountModule saam = new SelectActiveAccountModule(
					this, message);
			this.sendMessage(saam.handleRequest());
			this.server
					.updateDataObject(
							"User: " + message.getString("username")
									+ " has selected "
									+ message.getString("active_accountno"),
							null, null);
			break;

		case MSG.BALANCE_REQUEST:
			AccountBalanceModule abm = new AccountBalanceModule(this, message);
			this.sendMessage(abm.handleRequest());
			break;

		case MSG.INIT_FUNDSTRANSFER:
			InitTransactionModule itm = new InitTransactionModule(this, message);
			this.sendMessage(itm.handleRequest());
			break;

		case MSG.CONFIRM_TRANSACTION:
			ConfirmTransactionModule ctm = new ConfirmTransactionModule(this,
					message);
			this.sendMessage(ctm.handleRequest());
			break;

		case MSG.VIEW_TRANSACTIONS:
			ViewPendingTransactionModule vptm = new ViewPendingTransactionModule(
					this, message);
			this.sendMessage(vptm.handleRequest());
			break;

		case MSG.ANNOUNCEMENTS:
			AnnouncementModule am = new AnnouncementModule(this, message);
			this.sendMessage(am.handleRequest());
			break;

		default:
			System.out.println("Unkown request");
			break;
		}

		// System.out.println(clientip + ": " + msg);
		// if (msg.charAt(0) != '/') {
		// return;
		// }
		// int messagetype = (int) msg.charAt(1);
		//
		// switch (messagetype) {
		//
		// case MSG.LOGIN_REQUEST:
		// System.out.println("ClientHandler" + clientip + ": Login request");
		// if (!this.isLoggedIn()) {
		// Login login = new Login(this, this.networkswitch, msg);
		// }
		// break;

		// case Message.BALANCE_REQUEST:
		// new AccountBalanceModule(this, networkswitch, msg);
		// break;
		//
		// case Message.TRANSACTION_INIT_REQUEST:
		//
		// new FundsTransferModule(this, networkswitch, msg);
		//
		// break;

	}

	void sendMessage(Message message) throws Exception {
		this.networkswitch.send(message.toString());
	}

	// getters
	protected String getClientIP() {
		return this.clientIP;
	}

	public Session getSession() {
		return this.session;
	}

	public boolean isLoggedIn() {
		return (session != null);
	}

	// setters
	protected void setClientIP(String clientIP) {
		this.clientIP = clientIP;
	}
}
